﻿using System;
using System.Collections;
using CommonTypes.Client;
using CommonTypes.Server;
using System.Reflection;
using Client.Model;
using Client.Threads;
using System.Threading;

namespace Client.RemoteObjects
{
    public class ClientToClient : MarshalByRefObject, IClientToClient
    {
        private String _clientId;

        private ClientData _clientData;

        public ClientToClient(ClientData clientData)
        {
            _clientId = clientData.ClientId;
            _clientData = clientData;
        }

        public void BeginReservation(Reservation reservation, ArrayList receivedClients, ArrayList lastClients)
        {
            System.Console.WriteLine("Receiving Begin Reservation Request: " + reservation);

            ArrayList availableSlots = new ArrayList();

            foreach (ReservationSlot resSlot in reservation.ReservationSlots)
            {
                bool reservationSucceeded = _clientData.TrySlotReservation(resSlot.SlotNumber, reservation);
                if (reservationSucceeded)
                    availableSlots.Add(resSlot.SlotNumber);
            }

            receivedClients.Add(_clientId);
            lastClients.Remove(_clientId);

            IClientToClient clientToClient = GetClientProxy(reservation.InitiatorId);
            clientToClient.ReservationAnswer(receivedClients, _clientId, availableSlots, reservation.TicketNumber);

            if (lastClients.Count != 0)
            {
                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("BeginReservation");

                object[] args = new object[] { reservation, receivedClients, lastClients };
                MessageDissemination.disseminateMessage(methodInf, _clientData, lastClients, args, false);
            }
        }

        public void ReservationAnswer(ArrayList receivedClients, String clientId, ArrayList availableSlots, long reservationTicket)
        {
            System.Console.WriteLine("Receiving Reservation Answer from: " + clientId + ". Number of available slots: " + availableSlots.Count);

            ReservationInitiatorThread reservationThread = _clientData.GetReservationThread(reservationTicket);
            Request request = new Request(clientId, availableSlots);
            reservationThread.AddClientRequest(request);
        }

        public void NotifyTentativelyBooked(String initiatorId, long ticketNumber, long slotNumber, ArrayList lastClients, Reservation reservation)
        {
            Console.WriteLine("Receiving Tentatively Booked from {0} to reservation {1} slot {2}", initiatorId, ticketNumber, slotNumber);

            bool tBSucessful = _clientData.TryTentativelyBooked(slotNumber, ticketNumber);
            ReservationClientThread reservationClientThreadObject = new ReservationClientThread(_clientData, slotNumber, reservation);

            ThreadStart reservationClientThreadStart = new ThreadStart(reservationClientThreadObject.Run);
            _clientData.AddReservationClientThread(ticketNumber, reservationClientThreadObject);

            Thread reservationClientThread = new Thread(reservationClientThreadStart);
            reservationClientThreadObject.ThreadObject = reservationClientThread;
            reservationClientThread.IsBackground = true;
            reservationClientThread.Start();

            IClientToClient initiatorProxy = GetClientProxy(initiatorId);
            initiatorProxy.TentativelyBookedAnswer(_clientId, ticketNumber, slotNumber, tBSucessful);
            lastClients.Remove(_clientId);

            if (lastClients.Count != 0)
            {
                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("NotifyTentativelyBooked");

                object[] args = new object[] { initiatorId, ticketNumber, slotNumber, lastClients, reservation };
                MessageDissemination.disseminateMessage(methodInf, _clientData, lastClients, args, false);
            }
        }

        public void TentativelyBookedAnswer(String clientId, long ticketNumber, long slotNumber, bool answer)
        {
            Console.WriteLine("Received tentatively booked answer from {0} to slot {1}: {2}", clientId, slotNumber, answer);

            ReservationInitiatorThread resThread = _clientData.GetReservationThread(ticketNumber);
            Request request = new Request(clientId, answer, slotNumber);
            resThread.AddClientRequest(request);
        }

        public void VoteCommitRequest(String initiatorId, long ticketNumber, long slotNumber, long transactionTimeout, ArrayList lastClients)
        {
            Console.WriteLine("Received Vote Commit Request from {0} to slot {1} of reservation {2}", initiatorId, slotNumber, ticketNumber);

            ReservationClientThread reservationThread = _clientData.GetReservationClientThread(ticketNumber);
            reservationThread.TransactionTimeout = transactionTimeout;
            Request request = new Request(RequestPhase.REQUEST_COMMIT, slotNumber);
            reservationThread.AddInitiatorRequest(request);

            lastClients.Remove(_clientId);

            if (lastClients.Count > 0)
            {
                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("VoteCommitRequest");

                object[] args = new object[] { initiatorId, ticketNumber, slotNumber, transactionTimeout, lastClients };
                MessageDissemination.disseminateMessage(methodInf, _clientData, lastClients, args, false);
            }
        }

        public void CommitRequestAnswer(String clientId, long ticketNumber, long slotNumber, bool answer)
        {
            Console.WriteLine("Received Vote Commit Request answer from {0} to slot {1}: {2}", clientId, slotNumber, answer);

            ReservationInitiatorThread resThread = _clientData.GetReservationThread(ticketNumber);
            Request request = new Request(clientId, answer, slotNumber, false);
            resThread.AddClientRequest(request);
        }

        public void VoteDoCommit(String initiatorId, long ticketNumber, long slotNumber, ArrayList lastClients)
        {
            Console.WriteLine("Received Do Commit from {0} to slot {1} of reservation {2}", initiatorId, slotNumber, ticketNumber);

            ReservationClientThread reservationThread = _clientData.GetReservationClientThread(ticketNumber);
            Request request = new Request(RequestPhase.COMMIT, slotNumber);
            reservationThread.AddInitiatorRequest(request);

            lastClients.Remove(_clientId);

            if (lastClients.Count > 0)
            {
                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("VoteDoCommit");

                object[] args = new object[] { initiatorId, ticketNumber, slotNumber, lastClients };
                MessageDissemination.disseminateMessage(methodInf, _clientData, lastClients, args, false);
            }
        }

        public void DoCommitAnswer(String clientId, long ticketNumber, long slotNumber, bool answer)
        {
            Console.WriteLine("Received Do Commit answer from {0} to slot {1}: {2}", clientId, slotNumber, answer);

            ReservationInitiatorThread resThread = _clientData.GetReservationThread(ticketNumber);
            Request request = new Request(clientId, answer, slotNumber, true);
            resThread.AddClientRequest(request);
        }

        public void AbortTransaction(String initiatorId, long ticketNumber, long slotNumber, ArrayList lastClients)
        {
            Console.WriteLine("Received Abort from {0} to slot {1} of reservation {2}", initiatorId, slotNumber, ticketNumber);

            ReservationClientThread reservationThread = _clientData.GetReservationClientThread(ticketNumber);

            //If the abort arrives before the TB the thread doesn't exist yet...so we wait a bit for the coming TB
            int tries = 100, count = 0;
            while (reservationThread == null && count < tries)
            {
                Thread.Sleep(50);
                reservationThread = _clientData.GetReservationClientThread(ticketNumber);
                count++;
            }

            Request request = new Request(RequestPhase.ABORT, slotNumber);
            reservationThread.AddInitiatorRequest(request);

            /*if (reservationThread.ThreadState == TransactionClientState.CANCELED)
            {
                reservationThread.ClearSlots();
            }*/
            
            lastClients.Remove(_clientId);

            if (lastClients.Count > 0)
            {
                Type typExternal = typeof(CommonTypes.Client.IClientToClient);
                MethodInfo methodInf = typExternal.GetMethod("AbortTransaction");

                object[] args = new object[] { initiatorId, ticketNumber, slotNumber, lastClients };
                MessageDissemination.disseminateMessage(methodInf, _clientData, lastClients, args, false);
            }
        }

        public override object InitializeLifetimeService()
        {
            return null;
        }

        private IClientToClient GetClientProxy(String clientId)
        {
            Random random = new Random();
            IClientToServer clientToServer = _clientData.GetRandomServer();

            String clientAddress = clientToServer.ResolveClient(clientId);

            return (IClientToClient)Activator.GetObject(typeof(IClientToClient), clientAddress + "/ClientToClient");
        }
    }
}
